package com.wjm.http;

import java.io.*;
import java.net.Socket;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

/**
 * Data:2022年08月13日
 * Author:wjm
 * Content:
 *
 *     HttpResponse的每一个实例对象都是用来表示给客户端发送一个Http的响应
 *
 *     响应有三部分组成：
 *          状态行、响应头、响应正文
 *
 */
public class HttpResponse {
    //声明一个socket变量
    private Socket socket;

    /*
    * 状态行
    * */
    private int statusCode = 200; //状态码，默认200，表示资源存在
    private String statusReason = "OK"; //状态描述，

    /*
    * 响应头
    * */
    Map<String,String> headers = new HashMap<>();//设置一个集合，key存放Content-Type和Content-Length，value存放对应的值

    /*
    *  响应正文
    * */
    private File fileEntity;

    //字节输出流本质是内部维护了一个byte数组，是一个低级流
    private ByteArrayOutputStream baos = new ByteArrayOutputStream();
    //该流用于输出服务端响应的字符串内容
    private PrintWriter printWriter = new PrintWriter(baos);


    //get()和set()还有 构造方法
    public int getStatusCode() {
        return statusCode;
    }

    public void setStatusCode(int statusCode) {
        this.statusCode = statusCode;
    }

    public String getStatusReason() {
        return statusReason;
    }

    public void setStatusReason(String statusReason) {
        this.statusReason = statusReason;
    }

    public File getFileEntity() {
        return fileEntity;
    }

    public HttpResponse(Socket socket){
        this.socket = socket;
    }

    public PrintWriter getPrintWriter() {
        return printWriter;
    }//获取输出流的方法

    /**
     *  为响应正文的fileEntity赋值，
     *  并且根据文件的后缀名查找对应的Content-Type对应的值，最后将其添加到响应头的headers集合中
     * @param fileEntity 需要响应的资源文件的文件对象
     */
    public void setFileEntity(File fileEntity) {
        this.fileEntity = fileEntity;

        //获取文件名称
        String name = fileEntity.getName();
        //通过最后一个“.”点号来获取文件后缀,substring含头不含尾
        String type = name.substring(name.lastIndexOf(".")+1);
        //通过后缀名拿到对应的Content-Type表示的值
        String text = HttpContext.getMimeType(type);

        //往响应头集合headers里添加值
        putHeaders("Content-Type",text);
        putHeaders("Content-Length",fileEntity.length()+"");

    }


    /**
     * 统一管理和发送状态行、响应头、响应正文的方法
     */
    public void flush(){
        //在发送状态行、响应头、响应正文要做的必要准备
        beforeFlush();

        sendStatusLine();
        sendHeaders();
        sendContent();
    }

    /**
     * 将printWrite流中的数据刷新到低级流维护的byte数组中
     * 同时给响应头的headers集合中添加Content-Type和Content-Length
     */
    private void beforeFlush() {
        if (fileEntity == null){
            //将printWrite流中的数据刷新到低级流维护的byte数组中
            printWriter.flush();

            //获取低级流维护的byte数组
            byte[] bytes = baos.toByteArray();

            putHeaders("Content-Type","text/html");
            putHeaders("Content-Length",bytes.length+"");

        }
    }


    /**
     * 这个是一个往集合headers里放值的方法
     * @param name 存放Content-Type和Content-Length
     * @param value 存放与key对应的值
     */
    public void putHeaders(String name,String value){
        headers.put(name,value);
    }


    /**
     * 发送状态行的方法
     */
    public void sendStatusLine(){
        //状态行
        String line = "Http/1.1 "+statusCode+""+statusReason;
        //将状态行写出到输出流
        writeLine(line);
        System.out.println("状态行发送完毕！");

    }


    /**
     * 发送响应头的方法
     */
    public void sendHeaders(){
        //遍历消息头集合headers
        Set<Map.Entry<String,String>> entries = headers.entrySet();
        for (Map.Entry<String,String> m : entries){
            String key = m.getKey();
            String value = m.getValue();
            String line = key + ": " + value;
            //写出到输出流中
            writeLine(line);
        }

        //响应头全部发送结束，是以单独的连续的两个字符作为标识
        //out.write(13); //CR
        //out.write(10); //LF
        writeLine("");//表示另起一行空白行，效果与CRLF一样
        System.out.println("响应头发送完毕！");


    }


    /**
     * 发送响应正文的方法
     */
    public void sendContent() {
        //获取字节输出流内部维护了一个byte数组
        byte[] bytes = baos.toByteArray();

        //判读客户端请求的一个html文件，还是一堆可以被浏览器解析的字符串内容
        if (bytes.length>0){
            //数组长度大于0说明，客户端请求的是字符串
            try {
                OutputStream outputStream = socket.getOutputStream();
                //将数组中的内容写出去
                outputStream.write(bytes);

            } catch (IOException e) {
                e.printStackTrace();
            }


        }else if (fileEntity != null){//如果fileEntity不为null，则说明客户端请求的是一个文件
            try{
                //创建字节输入流对象读取，所需文件内容
                FileInputStream fileInputStream = new FileInputStream(fileEntity);
                //获取字节输出流
                OutputStream out =socket.getOutputStream();
                //创建一个读取文件的字节数组
                byte[] bytes1 = new byte[5*1024];
                int len;
                while ((len = fileInputStream.read(bytes1)) != -1){
                    //将读取到的内容写出
                    /*
                     * 因为只有字节数组bytes1装满的才会写出，所以为了解决这个问题，我们需要用重载的write方法
                     * 当用字节数组来读取文件时，len其实表示的就是字节数组bytes1的长度
                     * */
                    out.write(bytes1,0,len);
                }
                System.out.println("响应正文发送完毕！");
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }


    }


    /**
     * 这是一个写一行的方法，将需要发送的字符串传进来，并写出到输出流中
     * 封装了发送状态行方法(sendStatusLine)和发送响应头方法(sendHeaders)中的相同代码
     *
     * @param line 需要写出的字符串
     */
    public void writeLine(String line){
        try{
            //获取字节输出流
            OutputStream out =socket.getOutputStream();
            //将字符串转为字节数组
            byte[] bytes = line.getBytes("ISO8859-1");
            out.write(bytes);
            //状态行的结束是以两个连续的字符作为标识[CRLF]，响应头一行的结束也是以两个连续的字符作为标识[CRLF]
            out.write(13); //CR
            out.write(10); //LF
        } catch (IOException e) {
            e.printStackTrace();
        }
    }


}
